
/******************************************************************************
 * MODULE     : stack.cpp
 * DESCRIPTION: Boxes stacked one above another
 * COPYRIGHT  : (C) 1999  Joris van der Hoeven
 *******************************************************************************
 * This software falls under the GNU general public license version 3 or later.
 * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
 * in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.
 ******************************************************************************/

#include "Boxes/composite.hpp"

/******************************************************************************
 * The stack_box representation
 ******************************************************************************/

struct stack_box_rep : public composite_box_rep {
  stack_box_rep (path ip, array<box> bs, array<SI> spc);
  int get_type () { return STACK_BOX; }
  operator tree () {
    int  i, n= N (bs);
    tree t (TUPLE, "stack");
    for (i= 0; i < n; i++)
      t << ((tree) bs[i]);
    return t;
  }

  void position (array<SI> spc);
  void finalize ();
  void display (renderer ren);
  void clear_incomplete (rectangles& rs, SI pixel, int i, int i1, int i2);
  bool access_allowed ();

  int           find_child (SI x, SI y, SI delta, bool force);
  path          find_tree_path (path bp);
  cursor        find_cursor (path bp);
  selection     find_selection (path lbp, path rbp);
  gr_selections graphical_select (SI x, SI y, SI dist);
  gr_selections graphical_select (SI x1, SI y1, SI x2, SI y2);
};

/******************************************************************************
 * Stack boxes
 ******************************************************************************/

void
stack_box_rep::position (array<SI> spc) {
  int i;
  y1= y2= 0;
  for (i= 0; i < N (bs); i++) {
    sx (i)= 0;
    sy (i)= y1 - bs[i]->y2;
    y1-= (bs[i]->h () + spc[i]);
  }
  composite_box_rep::position ();
}

stack_box_rep::stack_box_rep (path ip, array<box> bs2, array<SI> spc)
    : composite_box_rep (ip) {
  bs= bs2;
  if (N (bs) != 0) position (spc);
  finalize ();
}

void
stack_box_rep::finalize () {
  path old_ip= ip;
  ip         = decorate_middle (ip);
  composite_box_rep::finalize ();
  ip= old_ip;
}

void
stack_box_rep::display (renderer ren) {
  ren->apply_shadow (x1, y1, x2, y2);
}

bool
stack_box_rep::access_allowed () {
  return false;
}

void
stack_box_rep::clear_incomplete (rectangles& rs, SI pixel, int which, int i1,
                                 int i2) {
  if (N (bs) == 0) return;
  if ((i1 <= i2) && (!is_nil (rs))) {
    // cout << "Stack " << which << " ( " << i1 << " - " << i2 << " )" << LF;
    // cout << "  in: " << (rs/256) << LF;

    int       i, n= N (bs);
    rectangle bound= least_upper_bound (rs);
    SI        left= bound->x1, right= bound->x2;
    for (i= 0; i < n; i++) {
      left = min (left, sx3 (i));
      right= max (right, sx4 (i));
    }
    if ((which >= 0) && (which < n)) {
      rectangle& r= access_last (rs);
      if (r->x2 >= sx4 (which)) r->x2= right;
    }

    /*
    SI Min_y= min_y, Max_y= max_y;
    if (i2+1<n) Min_y= sy4 (i2+1) + 2*pixel;
    if (i1  >0) Max_y= sy3 (i1-1) - 2*pixel;
    // cout << "  ys : " << Min_y/256 << ", " << min_y/256 << ", " << max_y/256
    << ", " << Max_y/256 << "\n";
    */

    SI min_y= sy4 (i2) + 2 * pixel, max_y= sy3 (i1) - 2 * pixel;
    if ((min_y < max_y) && (bound->y1 < min_y) && (max_y < bound->y2)) {
      rectangles new_rs;
      rectangles count= rs;
      while (!is_nil (count)) {
        rectangle& r= count->item;
        if ((r->y1 <= min_y) || (r->y2 >= max_y))
          new_rs= rectangles (r, new_rs);
        count= count->next;
      }
      new_rs= rectangles (rectangle (left, min_y, right, max_y), new_rs);
      rs    = reverse (new_rs);
    }

    // cout << "  out: " << (rs/256) << "\n\n";
  }
}

/******************************************************************************
 * Cursor routines
 ******************************************************************************/

int
stack_box_rep::find_child (SI x, SI y, SI delta, bool force) {
  int i, h, n= N (bs);
  if (n == 0) return -1;
  else if (n < 4) i= 0;
  else {
    i= h= n >> 1;
    while (h != 0) {
      SI yhi= sy1 (i);
      SI ylo= sy2 (i + 1);
      if (h == 1) h= 0;
      else h= (h + 1) >> 1;
      if ((y < yhi) && (y < ylo)) i+= h;
      else i-= max (h, 1);
      if (i < 0) i= 0;
      if (i > n - 2) i= n - 2;
    }
  }

  for (; i < n; i++) {
    if (i < n - 1) {
      box sb1= bs[i];
      box sb2= bs[i + 1];
      SI  yhi= sy1 (i);
      SI  ylo= sy2 (i + 1);
      // cout << "Test [" << i << "] " << sb1 << ", " << sb2 << "\n";
      // cout << "  y  = " << y   << "\n";
      // cout << "  yhi= " << yhi << "\n";
      // cout << "  ylo= " << ylo << "\n";
      if ((y < yhi) && (y < ylo)) continue;
      if (ylo <= yhi) {
        if (y < ((ylo + yhi) >> 1)) continue;
      }
      else {
        int j;
        SI  m= ylo, M= yhi;
        SI  ox1= sx (i), ox2= sx (i + 1);
        SI  oy1= sy (i), oy2= sy (i + 1);
        while ((N (sb1) == 1) && (N (sb2) == 1)) {
          ox1+= sb1->sx (0);
          ox2+= sb2->sx (0);
          oy1+= sb1->sy (0);
          oy2+= sb2->sy (0);
          sb1= sb1[0];
          sb2= sb2[0];
        }
        if ((N (sb1) > 0 && N (sb1[0]) == 0) ||
            (N (sb2) > 0 && N (sb2[0]) == 0))
          if (y < ((ylo + yhi) >> 1)) continue;
        for (j= 0; j < N (sb1); j++)
          if (!outside (x - ox1 - sb1->sx (j), delta, sb1[j]->x1, sb1[j]->x2))
            m= min (m, oy1 + sb1->sy1 (j));
        for (j= 0; j < N (sb2); j++)
          if (!outside (x - ox2 - sb2->sx (j), delta, sb2[j]->x1, sb2[j]->x2))
            M= max (M, oy2 + sb2->sy2 (j));
        if (y < ((m + M) >> 1)) continue;
      }
    }
    // cout << "Done [" << i << "]\n";

    if (bs[i]->decoration () && (!force)) {
      int j, k;
      for (j= i - 1; j >= 0; j--)
        if (bs[j]->accessible () || force) break;
      for (k= i + 1; k < n; k++)
        if (bs[k]->accessible () || force) break;
      if ((j < 0) && (k >= n)) return -1;
      if ((j >= 0) && (k >= n)) return j;
      if ((j < 0) && (k < n)) return k;
      if (y >= ((sy1 (j) + sy2 (k)) >> 1)) return j;
      return k;
    }

    return i;
  }

  return -1;
}

path
stack_box_rep::find_tree_path (path bp) {
  if (is_atom (bp)) {
    if (bp->item == 0) {
      if (is_accessible (lip)) return reverse (lip);
      else return reverse (descend_decode (lip, 0));
    }
    else {
      if (is_accessible (rip)) return reverse (rip);
      else return reverse (descend_decode (rip, 1));
    }
  }
  else return composite_box_rep::find_tree_path (bp);
}

cursor
stack_box_rep::find_cursor (path bp) {
  cursor cu= composite_box_rep::find_cursor (bp);
  int    i= bp->item, j1, j2, n= N (bs);
  if (is_atom (bp)) i= (as_tree (bp) == 0 ? 0 : N (bs) - 1);
  if (bs[i]->h () != 0) return cu;
  for (j1= i - 1; j1 >= 0; j1--)
    if (bs[j1]->h () != 0) break;
  for (j2= i + 1; j2 < n; j2++)
    if (bs[j2]->h () != 0) break;
  if (j2 < n) cu->oy+= sy (j2) - sy (i);
  else if (j1 >= 0) cu->oy+= sy (j1) - sy (i);
  return cu;
}

/******************************************************************************
 * Selections
 ******************************************************************************/

static rectangles
descend (rectangles l, SI y) {
  if (is_nil (l)) return l;
  rectangle& r= l->item;
  return rectangles (rectangle (r->x1, min (r->y1, y), r->x2, r->y2),
                     descend (l->next, y));
}

static rectangles
ascend (rectangles l, SI y) {
  if (is_nil (l)) return l;
  rectangle& r= l->item;
  return rectangles (rectangle (r->x1, r->y1, r->x2, max (r->y2, y)),
                     ascend (l->next, y));
}

static rectangles
extend_left (rectangles l, SI x) {
  if (is_nil (l)) return l;
  rectangle& r= l->item;
  return rectangles (rectangle (min (r->x1, x), r->y1, r->x2, r->y2),
                     extend_left (l->next, x));
}

static rectangles
extend_right (rectangles l, SI x) {
  if (is_nil (l)) return l;
  rectangle& r= l->item;
  return rectangles (rectangle (r->x1, r->y1, max (r->x2, x), r->y2),
                     extend_right (l->next, x));
}

static rectangles
truncate_top (rectangles l, SI y) {
  if (is_nil (l)) return l;
  rectangle& r= l->item;
  return rectangles (rectangle (r->x1, r->y1, r->x2, min (r->y2, y)),
                     truncate_top (l->next, y));
}

static rectangles
truncate_bottom (rectangles l, SI y) {
  if (is_nil (l)) return l;
  rectangle& r= l->item;
  return rectangles (rectangle (r->x1, max (r->y1, y), r->x2, r->y2),
                     truncate_bottom (l->next, y));
}

selection
stack_box_rep::find_selection (path lbp, path rbp) {
  if ((N (bs) == 0) ||
      ((!is_atom (lbp)) && (!is_atom (rbp)) && (lbp->item == rbp->item)))
    return composite_box_rep::find_selection (lbp, rbp);

  int  i1  = is_atom (lbp) ? 0 : lbp->item;
  int  i2  = is_atom (rbp) ? N (bs) - 1 : rbp->item;
  path lbp1= is_atom (lbp) ? path (i1, bs[i1]->find_left_box_path ()) : lbp;
  path rbp1= path (i1, bs[i1]->find_right_box_path ());
  path lbp2= path (i2, bs[i2]->find_left_box_path ());
  path rbp2= is_atom (rbp) ? path (i2, bs[i2]->find_right_box_path ()) : rbp;

  if (i1 == i2) {
    path lp= find_tree_path (lbp);
    path rp= find_tree_path (rbp);
    return selection (find_selection (lbp1, rbp2)->rs, lp, rp);
  }
  else if (i1 < i2) {
    selection  sel1 = find_selection (lbp1, rbp1);
    selection  sel2 = find_selection (lbp2, rbp2);
    path       lp   = sel1->start;
    path       rp   = sel2->end;
    SI         midy1= sy1 (i2 - 1);
    SI         midy2= sy2 (i1 + 1);
    rectangles rs;
    if (i2 == i1 + 1) {
      rs << extend_right (sel1->rs, x2);
      rs << extend_left (sel2->rs, x1);
      if ((!is_nil (sel1->rs)) && (!is_nil (sel2->rs))) {
        rectangle r1= least_upper_bound (sel1->rs);
        rectangle r2= least_upper_bound (sel2->rs);
        if ((r1->x1 < r2->x2) && (r2->y2 < r1->y1))
          rs << rectangle (r1->x1, r2->y2, r2->x2, r1->y1);
      }
    }
    else {
      rs << extend_right (descend (sel1->rs, midy2), x2);
      rs << extend_left (ascend (sel2->rs, midy1), x1);
      if (midy1 < midy2) rs << rectangle (x1, midy1, x2, midy2);
    }

    /* This hack produces nicer selections in case of a hidden top/bottom */
    int j1= i1, j2= i2;
    while (j1 < i2 && bs[j1]->h () == 0)
      j1++;
    while (j2 > j1 && bs[j2]->h () == 0)
      j2--;
    if (j1 != i1) rs= truncate_top (rs, sy2 (j1));
    if (j2 != i2) rs= truncate_bottom (rs, sy1 (j2));
    /* End hack */

    return selection (rs, lp, rp);
  }
  else return box_rep::find_selection (lbp, rbp);
}

gr_selections
stack_box_rep::graphical_select (SI x, SI y, SI dist) {
  gr_selections res;
  if (graphical_distance (x, y) <= 5000 * PIXEL) {
    // NOTE : increased threshold needed for draw-over
    // FIXME: we might want to use a dynamic threshold instead
    int i, n= subnr ();
    for (i= n - 1; i >= 0; i--)
      res << bs[i]->graphical_select (x - sx (i), y - sy (i), dist);
  }
  return res;
}

gr_selections
stack_box_rep::graphical_select (SI x1, SI y1, SI x2, SI y2) {
  gr_selections res;
  int           i, n= subnr ();
  for (i= n - 1; i >= 0; i--)
    res << bs[i]->graphical_select (x1 - sx (i), y1 - sy (i), x2 - sx (i),
                                    y2 - sy (i));
  return res;
}

/******************************************************************************
 * box construction routines
 ******************************************************************************/

box
stack_box (path ip, array<box> bs, array<SI> spc) {
  return tm_new<stack_box_rep> (ip, bs, spc);
}
